﻿using System;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using SocketControl.Iocp.Buffer;
using SocketControl.Iocp.Pool;
using SocketControl.Log;
using System.Text;

namespace SocketControl.Iocp
{
    public class Client
    {
        #region Socket定义
        private System.Net.Sockets.Socket _mSocket = null;
        #endregion

        #region 基本属性定义
        private int _mRecvBuffer; //接收数据缓存
        private readonly Semaphore _mSemaphore;//连接信号量;
        private int _mTotalBytesRead;//读取数据包总长度;
        private int _mNumConnectedSockets;//当前连接数;

        /// <summary>
        /// 设置或获取是否回包
        /// </summary>
        public bool IsBackParket = true;
        #endregion

        #region PUBLIC
        /// <summary>
        /// 获取当前连接数；
        /// </summary>
        public int GetConnectedNum { get { return _mNumConnectedSockets; } }
        public long TotalBytes { get { return _mTotalBytesRead; } }
        #endregion

        #region

        private const int OpsToPreAlloc = 2;//??
        private readonly SocketAsyncEventArgsPool _mAsyncEventArgsPool;//消息线程池；
        #endregion

        #region Event
        public EventHandler<SocketAsyncEventArgs> OnRecvCompleted;
        public EventHandler<SocketAsyncEventArgs> OnSendCompleted;
        public EventHandler<SocketAsyncEventArgs> OnClose;
        public EventHandler<SocketAsyncEventArgs> OnConnectionSuccess;
        #endregion

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="numConnections"></param>
        /// <param name="bufferSize"></param>
        public Client(int numConnections, int bufferSize)
        {
            _mTotalBytesRead = 0;
            _mNumConnectedSockets = 0;
            _mRecvBuffer = bufferSize;
            // 建立缓存对象
            var mBufferManager = new BufferManager(bufferSize * numConnections * OpsToPreAlloc,
                bufferSize);
            //建立线程池
            _mAsyncEventArgsPool = new SocketAsyncEventArgsPool(numConnections);
            _mSemaphore = new Semaphore(numConnections, numConnections);
            //---------------------------------------------------------
            // 初始化缓存
            mBufferManager.InitBuffer();
            for (int i = 0; i < numConnections; i++)
            {
                //实例化SocketAsyncEventArgs对象
                var readWriteEventArg = new SocketAsyncEventArgs();
                readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
                readWriteEventArg.UserToken = new AsyncUserToken();

                // SocketAsyncEventArgs 分配缓存；
                mBufferManager.SetBuffer(readWriteEventArg);

                // SocketAsyncEventArgs放入线程池
                _mAsyncEventArgsPool.Push(readWriteEventArg);
            }
        }

        private void IO_Completed(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    {
                        ProcessReceive(e);
                        break;
                    }
                case SocketAsyncOperation.Send:
                    {
                        ProcessSend(e);
                        break;
                    }
                case SocketAsyncOperation.Connect:
                    {
                        ProcessConnection(e);
                        break;
                    }
                default:
                    {
                        break;
                    }
            }
        }

        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            try
            {
                var token = (AsyncUserToken)e.UserToken;
                if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
                {
                    //increment the count of the total bytes receive by the server
                    Interlocked.Add(ref _mTotalBytesRead, e.BytesTransferred);

                    if (OnRecvCompleted != null)
                    {
                        try
                        {
                            OnRecvCompleted(this, e);
                        }
                        catch (Exception evente)
                        {
                            LogHelper.WriteError(this.GetType(), evente);
                        }
                    }
                    if (e.SocketError == SocketError.Success)
                    {
                        ProcessSend(e);
                    }
                    else
                    {
                    }
                }
            }
            catch (Exception recve)
            {
                LogHelper.WriteError(this.GetType(), recve);
            }
        }

        private void ProcessSend(SocketAsyncEventArgs e)
        {
            try
            {
                var token = (AsyncUserToken)e.UserToken;
                if (e != null && e.SocketError == SocketError.Success)
                {
                    if (OnSendCompleted != null)
                    {
                        try
                        {
                            OnSendCompleted(this, e);
                        }
                        catch (Exception evente)
                        {
                            LogHelper.WriteError(this.GetType(), evente);
                        }
                    }
                    if (e.SocketError == SocketError.Success)
                    {
                        if (token.Socket.Connected)
                        {
                            bool willRaiseEvent = token.Socket.ReceiveAsync(e);
                            if (!willRaiseEvent)
                            {
                                ProcessReceive(e);
                            }
                        }
                    }
                }
            }
            catch (Exception sende)
            {
                LogHelper.WriteError(this.GetType(), sende);
            }
        }

        public void Connection(string ip, int port, byte[] buff)
        {
            _mSemaphore.WaitOne();
            var mConn = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            var cArgs = _mAsyncEventArgsPool.Pop();
            try
            {

                cArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
                cArgs.SetBuffer(buff, 0, buff.Length);
                ((AsyncUserToken)cArgs.UserToken).Socket = mConn;
                bool willRaiseEvent = ((AsyncUserToken)cArgs.UserToken).Socket.ConnectAsync(cArgs);
                if (!willRaiseEvent)
                {
                    LogHelper.WriteInfo(this.GetType(), "connection error!");
                    ProcessConnection(cArgs);
                }
                mConn = null;
            }
            catch (Exception connectione)
            {
                LogHelper.WriteError(this.GetType(), connectione);
            }
        }

        public void ProcessConnection(SocketAsyncEventArgs e)
        {
            Interlocked.Increment(ref _mNumConnectedSockets);
            var token = (AsyncUserToken)e.UserToken;
            LogHelper.WriteInfo(this.GetType(), "\r\nConnected:" + e.RemoteEndPoint.ToString() + "\r\n");
            if (e.SocketError == SocketError.Success)
            {
                if (OnConnectionSuccess != null)
                {
                    try
                    {
                        OnConnectionSuccess(this, e);
                    }
                    catch (Exception evente)
                    {
                        LogHelper.WriteError(this.GetType(), evente);
                    }
                }
                bool willRaiseEvent = token.Socket.ReceiveAsync(e);
                if (!willRaiseEvent)
                {
                    ProcessReceive(e);
                }
            }
            else
            {
                LogHelper.WriteInfo(this.GetType(), "Close Sokcet");
            }
        }

        /// <summary>
        /// 关闭一个连接
        /// </summary>
        /// <param name="e">SocketAsyncEventArgs</param>
        public void CloseSocket(SocketAsyncEventArgs e)
        {

            var token = e.UserToken as AsyncUserToken;
            // close the socket associated with the client
            try
            {
                ((AsyncUserToken)e.UserToken).Socket.Shutdown(SocketShutdown.Both);
            }
            // throws if client process has already closed
            catch (Exception ex)
            {
                ;
            }
                ((AsyncUserToken)e.UserToken).Socket.Close();
            // decrement the counter keeping track of the total number of clients connected to the server
            Interlocked.Decrement(ref _mNumConnectedSockets);

            _mSemaphore.Release();
            // Free the SocketAsyncEventArg so they can be reused by another client
            _mAsyncEventArgsPool.Push(e);
            if (e.SocketError == SocketError.Success)
            {
                if (OnClose != null)
                {
                    OnClose(this, e);
                }
            }
        }
    }
}